package com.zqq.netty;

import android.util.Log;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.internal.StringUtil;

public class NettyManager {

    private static final String TAG = "bl_netty";
    private ChannelFuture channelFuture;
    private String ip;
    private int port;
    private int dataTimeOut; //收发超时
    private int readTimeOut; // 读超时时间
    private int whiteTimeOut; // 写超时时间
    private ChannelFutureListener channelFutureListener;
    private NioEventLoopGroup nioEventLoopGroup;
    private Bootstrap bootstrap;

    private int connectFailNum = 0;

    private int lengthFieldOffset = 3; //长度域偏移。就是说数据开始的几个字节可能不是表示数据长度，需要后移几个字节才是长度域
    private int lengthFieldLength = 2; //长度域字节数。用几个字节来表示数据长度。
    private int lengthAdjustment = 1; //数据长度修正。因为长度域指定的长度可以使header+body的整个长度，也可以只是body的长度。如果表示header+body的整个长度，那么我们需要修正数据长度。
    private int initialBytesToStrip = 0; //跳过的字节数。如果你需要接收header+body的所有数据，此值就是0，如果你只想接收body数据，那么需要跳过header所占用的字节数。

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public NettyManager(String ip, int port, int dataTimeOut, int readTimeOut, int whiteTimeOut) {
        this(ip, port, dataTimeOut, readTimeOut, whiteTimeOut, 3, 2, 1, 0);
    }

    /**
     * @param dataTimeOut  收发超时
     * @param readTimeOut  读超时时间
     * @param whiteTimeOut 写超时时间
     */
    public NettyManager(String ip, int port, int dataTimeOut, int readTimeOut, int whiteTimeOut, int lengthFieldOffset, int lengthFieldLength,
                        int lengthAdjustment, int initialBytesToStrip) {
        this.ip = ip;
        this.port = port;
        this.dataTimeOut = dataTimeOut;
        this.readTimeOut = readTimeOut;
        this.whiteTimeOut = whiteTimeOut;
        this.lengthFieldOffset = lengthFieldOffset;
        this.lengthFieldLength = lengthFieldLength;
        this.lengthAdjustment = lengthAdjustment;
        this.initialBytesToStrip = initialBytesToStrip;


        Log.i(TAG, "create ip>>" + ip);
        Log.i(TAG, "create port>>" + port);
        Log.i(TAG, "create dataTimeOut>>" + dataTimeOut);
        Log.i(TAG, "create readTimeOut>>" + readTimeOut);
        Log.i(TAG, "create whiteTimeOut>>" + whiteTimeOut);

        //进行初始化
        //初始化线程组
        nioEventLoopGroup = new NioEventLoopGroup();
        bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class).group(nioEventLoopGroup);
        bootstrap.option(ChannelOption.TCP_NODELAY, true); //无阻塞
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true); //长连接
        bootstrap.option(ChannelOption.SO_TIMEOUT, dataTimeOut); //收发超时
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, whiteTimeOut); //收发超时
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                if (lengthFieldOffset > 0) {
                    pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65530, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip));
                }
                pipeline.addLast("decoder", new ByteArrayDecoder())  //接收解码方式
                        .addLast("encoder", new ByteArrayEncoder())  //发送编码方式
                        .addLast(new ChannelHandle(NettyManager.this))//处理数据接收
                        .addLast(new IdleStateHandler(readTimeOut, whiteTimeOut, 0)); //心跳 参数1：读超时时间 参数2：写超时时间  参数3： 将在未执行读取或写入时触发超时回调，0代表不处理；读超时尽量设置大于写超时代表多次写超时时写心跳包，多次写了心跳数据仍然读超时代表当前连接错误，即可断开连接重新连接
            }
        });
    }


    private void create() {
        if (StringUtil.isNullOrEmpty(ip) || port == 0 || dataTimeOut == 0 || readTimeOut == 0 || whiteTimeOut == 0) {
            //TODO 设置回调通知service 重连
            if (onNettyListener != null) {
                onNettyListener.onNeedReCreate();
            }
            return;
        }

        if (channelFuture != null && channelFuture.channel().isActive()) {
            return;
        }
        //开始建立连接并监听返回
        try {
            channelFuture = bootstrap.connect(new InetSocketAddress(ip, port));
            channelFuture.addListener(channelFutureListener = new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    boolean success = future.isSuccess();
                    Log.i(TAG, "connect success>>" + success);
                    if (success) {
                        connectFailNum = 0;
                        Log.i(TAG, "connect success !");
                        if (onNettyListener != null) {
                            onNettyListener.onConnectSuccess();
                        }
                    } else { //失败
                        connectFailNum++;

                        future.channel().eventLoop().schedule(new Runnable() {
                            @Override
                            public void run() {
                                reConnect();
                            }
                        }, 5, TimeUnit.SECONDS);
                        if (onNettyListener != null) {
                            onNettyListener.onConnectFail(connectFailNum);
                        }
                    }
                }
            }).sync();

        } catch (Exception e) {
            Log.i(TAG, "e>>" + e);
            e.printStackTrace();
        }
    }

    /**
     * 发送数据
     *
     * @param sendBytes
     */
    public void sendData(byte[] sendBytes) {
        if (channelFuture == null) {
            return;
        }
        Log.i(TAG, "sendDataToServer");
        if (sendBytes != null && sendBytes.length > 0) {
            if (channelFuture != null && channelFuture.channel().isActive()) {
                Log.i(TAG, "writeAndFlush");
                channelFuture.channel().writeAndFlush(sendBytes);
            }
        }
    }


    public void receiveData(byte[] byteBuf) {
        Log.i(TAG, "receiveData>>" + byteBuf.toString());
        if (onNettyListener != null && byteBuf.length > 0) {
            onNettyListener.onReceiveData(byteBuf);
        }
    }

    public void connect() {
        Log.i(TAG, "connect ");
        if (channelFuture != null && channelFuture.channel() != null && channelFuture.channel().isActive()) {
            channelFuture.channel().close();//已经连接时先关闭当前连接，关闭时回调exceptionCaught进行重新连接
        } else {
            create(); //当前未连接，直接连接即可
        }
    }

    public void reConnect() {
        Log.i(TAG, "reConnect");
        if (StringUtil.isNullOrEmpty(ip) || port == 0 || dataTimeOut == 0 || readTimeOut == 0 || whiteTimeOut == 0) {
            //TODO 设置回调通知service 重连
            if (onNettyListener != null) {
                onNettyListener.onNeedReCreate();
            }
            return;
        }
        connect(); //当前未连接，直接连接即可
    }

    public void close() {
        Log.i(TAG, "close");
        if (channelFuture != null && channelFutureListener != null) {
            channelFuture.removeListener(channelFutureListener);
            channelFuture.cancel(true);
        }

        nioEventLoopGroup.shutdownGracefully();
    }

    private OnNettyListener onNettyListener;

    public void setOnNettyListener(OnNettyListener onNettyListener) {
        this.onNettyListener = onNettyListener;
    }

    public interface OnNettyListener {
        /**
         * 连接成功
         */
        void onConnectSuccess();

        /**
         * 连接失败
         */
        void onConnectFail(int connectFailNum);

        /**
         * 接收到数据
         *
         * @param bytes
         */
        void onReceiveData(byte[] bytes);

        /**
         * 参数丢失 需重新创建
         */
        void onNeedReCreate();
    }
}
